/*
 Xern Entity Relocation Network
 Copyright (C) 2010-2011 Ronie Salgado <roniesalg@gmail.com>

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
*/

// Primitive implementation is heavily (practically a port/adaptation)
// based in the Meshmerizer of LibOpenMetaverse written
// by dahlia <dahliatrimble@gmail.com>

#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <OgreSceneManager.h>
#include <OgreSubMesh.h>
#include <OgreHardwareBufferManager.h>
#include "Xern/DataTypes.hpp"
#include "Xern/Log.hpp"
#include "Xern/Matrix.hpp"
#include "Primitive.hpp"

namespace XernOgre
{
	enum ProfileCurve
	{
		PRC_CIRCLE = 0x00,
		PRC_SQUARE = 0x01,
		PRC_ISO_TRIANGLE = 0x02,
		PRC_EQUAL_TRIANGLE = 0x03,
		PRC_RIGHT_TRIANGLE = 0x04,
		PRC_HALF_CIRCLE = 0x05,
	};

	enum HoleType
	{
		HT_SAME = 0x00,
		HT_CIRCLE = 0x10,
		HT_SQUARE = 0x20,
		HT_TRIANGLE = 0x30,
	};

	enum PathCurve
	{
		PC_LINE = 0x10,
		PC_CIRCLE = 0x20,
		PC_CIRCLE2 = 0x30,
		PC_TEST = 0x40,
		PC_FLEXIBLE = 0x80,
	};

	enum SculptType
	{
		SCT_NONE = 0,
		SCT_SPHERE = 1,
		SCT_TORUS = 2,
		SCT_PLANE = 3,
		SCT_CYLINDER = 4,
		SCT_INVERT = 64,
		SCT_MIRROR = 128,
	};

	namespace PrimitiveData
	{
		using namespace XernOgre;

		class Triangle
		{
		public:
			Triangle()
			{
			}

			Triangle(int v1, int v2, int v3)
				: v1(v1), v2(v2), v3(v3)
			{
			}

			Triangle(const Triangle &t)
				: v1(t.v1), v2(t.v2), v3(t.v3)
			{
			}

			int v1, v2, v3;
		};

		class Angle
		{
		public:
			Angle()
				: angle(0.0f), x(1.0f), y(0.0f)
			{
			}

			Angle(float angle)
				: angle(angle), x(cosine(angle)), y(sine(angle))
			{
			}

			Angle(const Angle &a)
				: angle(a.angle), x(a.x), y(a.y)
			{
			}

			inline float cos() const
			{
				return x;
			}

			inline float sin() const
			{
				return y;
			}

			inline float tan() const
			{
				return y/x;
			}

			float angle, x, y;
		};

		inline void BuildAngleList(std::vector<Angle> &list, float start, float end, int steps)
		{
			list.clear();
			float stepAngle = (end - start)/float(steps);
			float angle = start;
			for(int i = 0; i <= steps; i++, angle += stepAngle)
			{
				list.push_back(Angle(angle));
			}
		}

		class Profile
		{
		private:
			Profile() {}

		public:
			typedef std::vector<Vector3> Coords;
			typedef std::vector<Vector2> TexCoords;
			typedef std::vector<Triangle> Faces;

			Profile(int sides, float profileStart, float profileEnd, float hollow, float hollowSides, bool createFaces)
			{
				const float T_PI = 2.0*X_PI;
				Vector3 center(0.0f);
				Coords hollowCoords;
				TexCoords hollowTexcoords;

				std::vector<Angle> angles;
				std::vector<Angle> hollowAngles;

				float xScale = 0.5f;
				float yScale = 0.5f;
				if(sides == 4) // Corners of a square are centered around sqrt(2);
				{
					xScale = yScale = M_SQRT1_2;
				}

				float startAngle = profileStart * T_PI;
				float stopAngle = profileEnd * T_PI;
				BuildAngleList(angles, startAngle, stopAngle, sides);

				if(hollow > X_EPSILON)
				{
					if(sides == hollowSides)
						hollowAngles = angles;
					else
						BuildAngleList(hollowAngles, startAngle, stopAngle, hollowSides);
				}
				else
				{
					texcoords.push_back(Vector2(0.0f, 0.0f));
					coords.push_back(center);
				}

				float z = 0.0f;
				Vector3 newVert;
				Vector2 newTex;
				if(hollow > X_EPSILON && hollowSides != sides)
				{
					size_t numHollowAngles = hollowAngles.size();
					for(size_t i = 0; i < numHollowAngles; i++)
					{
						const Angle &angle = hollowAngles[i];
						newVert.x = hollow * xScale * angle.x;
						newVert.y = hollow * yScale * angle.y;
						newVert.z = z;

						newTex.x = float(i)/float(numHollowAngles-1);
						newTex.y = 0.0f;

						hollowCoords.push_back(newVert);
						hollowTexcoords.push_back(newTex);
					}
				}

				int index = 0;
				size_t numAngles = angles.size();
				for(size_t i = 0; i < numAngles; i++)
				{
					const Angle &angle = angles[i];
					newVert.x = angle.x * xScale;
					newVert.y = angle.y * yScale;
					newVert.z = z;

					newTex.x = float(i)/float(numAngles-1);
					newTex.y = 0.0f;
					coords.push_back(newVert);
					texcoords.push_back(newTex);

					if(hollow > X_EPSILON)
					{
						if(hollowSides == sides)
						{
							newVert.x *= hollow;
							newVert.y *= hollow;
							newVert.z = z;
							hollowCoords.push_back(newVert);
							hollowTexcoords.push_back(newTex);
						}
					}
					else if(createFaces && angle.angle > X_EPSILON)
					{
						Triangle face;

						face.v1 = 0;
						face.v2 = index;
						face.v3 = index+1;
						faces.push_back(face);
					}
					index++;
				}

				if(hollow > X_EPSILON)
				{
					std::reverse(hollowCoords.begin(), hollowCoords.end());
					std::reverse(hollowTexcoords.begin(), hollowTexcoords.end());

					if(createFaces)
					{
						size_t numOuterVerts = coords.size();
						size_t numHollowVerts = hollowCoords.size();
						size_t numTotalVerts = numOuterVerts + numHollowVerts;

						if(numOuterVerts == numHollowVerts)
						{
							Triangle face;
							for(size_t coordIndex = 0; coordIndex < numOuterVerts - 1; coordIndex++)
							{
								face.v1 = coordIndex;
								face.v2 = coordIndex + 1;
								face.v3 = numTotalVerts - coordIndex - 1;
								faces.push_back(face);

								face.v1 = coordIndex +1;
								face.v2 = numTotalVerts - coordIndex - 2;
								face.v3 = numTotalVerts - coordIndex - 1;
								faces.push_back(face);
							}
						}
						else
						{
							if(numOuterVerts < numHollowVerts)
							{
								Triangle newFace;
								size_t j = 0; // Index for outer vertices
								size_t maxJ = numOuterVerts - 1;
								for(size_t i = 0; i < numHollowVerts; i++)
								{
									if(j < maxJ &&
										angles[j + 1].angle - hollowAngles[i].angle <=
										hollowAngles[i].angle - angles[j].angle)
									{
										newFace.v1 = numTotalVerts - i - 1;
										newFace.v2 = j;
										newFace.v3 = j + 1;

										faces.push_back(newFace);
										j++;
									}

									newFace.v1 = j;
									newFace.v2 = numTotalVerts - i - 2;
									newFace.v3 = numTotalVerts - i - 1;
									faces.push_back(newFace);
								}
							}
							else
							{
								Triangle newFace;
								size_t j = 0; // Index for inner vertices
								size_t maxJ = numHollowVerts - 1;
								for(size_t i = 0; i < numOuterVerts; i++)
								{
									if(j < maxJ &&
										hollowAngles[j + 1].angle - angles[i].angle <=
										angles[i].angle - hollowAngles[j].angle)
									{
										newFace.v1 = i;
										newFace.v2 = numTotalVerts - j - 2;
										newFace.v3 = numTotalVerts - j - 1;

										faces.push_back(newFace);
										j++;
									}

									newFace.v1 = numTotalVerts - j - 1;
									newFace.v2 = i;
									newFace.v3 = i + 1;
									faces.push_back(newFace);
								}
							}
						}
					}

					coords.insert(coords.end(),	hollowCoords.begin(), hollowCoords.end());
					texcoords.insert(texcoords.end(), hollowTexcoords.begin(), hollowTexcoords.end());
				}
			}

			Profile Clone()
			{
				return Clone(true);
			}

			Profile Clone(bool needFace)
			{
				Profile ret;
				ret.coords = coords;
				ret.texcoords = texcoords;
				if(needFace)
					ret.faces = faces;

				return ret;
			}

			void Translate(const Vector3 &offset)
			{
				size_t numcoords = coords.size();
				for(size_t i = 0; i < numcoords; i++)
				{
					coords[i] += offset;
				}
			}

			void TranslateTexcoords(const Vector2 &offset)
			{
				size_t numcoords = texcoords.size();
				for(size_t i = 0; i < numcoords; i++)
				{
					texcoords[i] += offset;
				}
			}

			void Rotate(const Quaternion &rotation)
			{
				Matrix3 trans = QRotMatrix3(rotation);
				size_t numcoords = coords.size();
				for(size_t i = 0; i < numcoords; i++)
				{
					coords[i] = trans*coords[i];
				}
			}

			void Scale(float x, float y)
			{
				size_t numcoords = coords.size();
				for(size_t i = 0; i < numcoords; i++)
				{
					coords[i].x *= x;
					coords[i].y *= y;
				}
			}

			void FlipNormals()
			{
				FlipWinding();
			}

			void FlipWinding()
			{
				size_t numfaces = faces.size();
				for(size_t i = 0; i < numfaces; i++)
				{
					int temp = faces[i].v3;
					faces[i].v3 = faces[i].v1;
					faces[i].v1 = temp;
				}
			}

			void OffsetIndices(int offset)
			{
				size_t numfaces = faces.size();
				for(size_t i = 0; i < numfaces; i++)
				{
					faces[i].v1 += offset;
					faces[i].v2 += offset;
					faces[i].v3 += offset;
				}
			}

			Coords coords;
			TexCoords texcoords;
			Faces faces;
		};
	};

	using namespace PrimitiveData;

	Primitive::Primitive(boost::shared_ptr<OgreRenderer> renderer,
		Ogre::SceneManager *scene, Ogre::MeshPtr mesh, int verticesWidth,
		int verticesHeight)
		: Geometry(renderer, scene, mesh), verticesWidth(verticesWidth), verticesHeight(verticesHeight)
	{
		sides = 4;
		hollowSides = 4;
		profileStart = 0.0f;
		profileEnd = 1.0f;
		twistBegin = 0;
		twistEnd = 0;
		topShearX = 0.0f;
		topShearY = 0.0f;
		pathCutBegin = 0.0f;
		pathCutEnd = 1.0f;
		dimpleBegin = 0.0f;
		dimpleEnd = 1.0f;
		skew = 0.0f;
		holeSizeX = 1.0f;
		holeSizeY = 0.0f;
		taperX = 0.0f;
		taperY = 0.0f;
		radius = 0.0f;
		revolutions = 1.0f;
		stepPerRevolution = 24;

		scale = Vector3(1.0f);
		sculptureType = 0;
		SetShadowCasting(false);
	}

	Primitive::~Primitive()
	{
	}

	const JsonObject &Primitive::GetParameters() const
	{
		return parameters;
	}

	void Primitive::SetParameters(const JsonObject &parameters)
	{
		// Store a copy of the parameters.
		this->parameters = parameters;
		//printf("prim = %s\n", boost::lexical_cast<std::string> (parameters).c_str());

		try
		{
			// Decode the parameters
			const JsonObject &volume = parameters.get<JsonObject> ("volume");

			// Read the path.
			const JsonObject &path = volume.get<JsonObject> ("path");
			pathCurve = path.get<JsonNumber> ("curve", PC_LINE);

			float pathShearX = path.get<JsonNumber> ("shear_x", 0.0f);
			float pathShearY = path.get<JsonNumber> ("shear_y", 0.0f);
			float pathBegin = path.get<JsonNumber> ("begin", 0.0f);
			float pathEnd = path.get<JsonNumber> ("end", 0.0f);
			float pathScaleX = path.get<JsonNumber> ("scale_x", 1.0f);
			float pathScaleY = path.get<JsonNumber> ("scale_y", 1.0f);
			float pathSkew = path.get<JsonNumber> ("skew", 0.0f);
			int pathTwistBegin = path.get<JsonNumber> ("twist_begin", 0);
			int pathTwist = path.get<JsonNumber> ("twist", 0);
			int pathRadiusOffset = path.get<JsonNumber> ("radius_offset", 0);
			int pathRevolutions = path.get<JsonNumber> ("revolutions", 0);

			// Read the profile.
			const JsonObject &profile = volume.get<JsonObject> ("profile");
			profileCurve = profile.get<JsonNumber> ("curve", PRC_SQUARE);
			holeType = profile.get<JsonNumber> ("hole", HT_SAME);

			profileStart = profile.get<JsonNumber> ("begin", 0.0f);
			profileEnd = profile.get<JsonNumber> ("end", 1.0f);
			profileHollow = profile.get<JsonNumber> ("hollow", 0.0f);

			// Read the scale.
			scale = parameters.get<JsonArray> ("scale", Vector3(0.0f)).ToVector3();
			rotation = parameters.get<JsonArray> ("rotation", Quaternion()).ToQuaternion();

			// Process the parameters.
			sides = 4;
			if(profileCurve == PRC_EQUAL_TRIANGLE)
				sides = 3;
			else if(profileCurve == PRC_CIRCLE)
				sides = 24;
			else if(profileCurve == PRC_HALF_CIRCLE)
			{
				// Half circle, primitive is a sphere.
				sides = 24;

				profileStart = 0.5*profileStart + 0.5f;
				profileEnd = 0.5*profileEnd + 0.5f;
			}

			hollowSides = sides;
			if(holeType == HT_CIRCLE)
				hollowSides = 24;
			else if(holeType == HT_SQUARE)
				hollowSides = 4;
			else if(holeType == HT_TRIANGLE)
				hollowSides = 3;

			topShearX = pathShearX;
			topShearY = pathShearY;
			pathCutBegin = pathBegin;
			pathCutEnd = pathEnd;

			if(!JsonIsNull(parameters.get("sculpt", JsonNull())))
				return;

			if(pathCurve == PC_LINE || pathCurve == PC_FLEXIBLE)
			{
				twistBegin = pathTwistBegin * 18/10;
				twistEnd = pathTwist * 18/10;
				taperX = pathScaleX;
				taperY = pathScaleY;

				ExtrudeLinear();
			}
			else
			{
				holeSizeX = 1.0f - pathScaleX;
				holeSizeY = 1.0f - pathScaleY;
				radius = pathRadiusOffset;
				revolutions = 1.0f + pathRevolutions;
				skew = pathSkew;
				twistBegin = pathTwistBegin*36/10;
				twistEnd = pathTwist*36/10;
				taperX = path.get<JsonNumber> ("taper_x", 0.0f);
				taperX = path.get<JsonNumber> ("taper_y", 0.0f);
				ExtrudeCircular();
			}
		}
		catch(const XernException &e)
		{
			LogConsole->Error("Failed to create a primitive. Incomplete parameters.");
		}
		catch(const boost::bad_get &e)
		{
			LogConsole->Error("Failed to create a primitive. Incomplete parameters.");
		}
	}

	int Primitive::GetSculptureType() const
	{
		return sculptureType;
	}

	void Primitive::SetSculptureType(int type)
	{
		sculptureType = type;
	}

	boost::shared_ptr<ITexture> Primitive::GetSculpture() const
	{
		return sculpture;
	}

	void Primitive::SetSculpture(boost::shared_ptr<ITexture> sculpt)
	{
		if(sculpture == sculpt)
			return;

		sculpture = sculpt;

		if(sculpture)
			BuildSculpture();
	}

	void Primitive::UploadVertices(int buffer, size_t len, const void *data)
	{
		// Shouldn't be called.
		assert(NULL);
	}

	void Primitive::ExtrudeLinear()
	{
		std::vector<Vector3> coords;
		std::vector<Vector2> texcoords;
		std::vector<Triangle> faces;

		const float T_PI = 2.0f*X_PI;

		int step = 0;
		int steps = 1;

		float length = pathCutEnd - pathCutBegin;
		float twistBegin = this->twistBegin/180.0f*X_PI;
		float twistEnd = this->twistEnd/180.0f*X_PI;
		float twistTotal = twistEnd - twistBegin;
		float twistTotalAbs = abs(twistTotal);
		if(twistTotalAbs > X_EPSILON)
			steps += int(twistTotalAbs*3.66f);

		float start = -0.5f;
		float stepSize = length/float(steps);
		float percentOfPathMultiplier = stepSize;
		Vector3 profileScale(1.0f);
		Vector3 offset(0.0f, 0.0f, start);
		Vector3 offsetStepIncrement(topShearX / steps, topShearY / steps, stepSize);

		float percentOfPath = this->pathCutBegin;
		offset.z += percentOfPath;

		float hollow = this->profileHollow;

		// Sanity checks.
		float initialProfileRot = 0.0f;
		if(sides == 3)
		{
			if(hollowSides == 4)
			{
				if(hollow > 0.7f)
					hollow = 0.7f;
				hollow *= M_SQRT1_2;
			}
			else
				hollow *= 0.5f;
		}
		else if(sides == 4)
		{
			initialProfileRot = 1.25f * X_PI;
			if(hollowSides != 4)
				hollow *= M_SQRT1_2;
		}
		else if(sides == 24 && hollowSides == 4)
			hollow *= M_SQRT2;

		Profile profile = Profile(sides, profileStart, profileEnd, hollow, hollowSides, true);
		if(CompareFloat(initialProfileRot, 0.0))
			profile.Rotate(Quaternion(0.0f, 0.0f, initialProfileRot));

		bool done = false;
		while(!done)
		{
			Profile newLayer = profile.Clone();

			if(CompareFloat(taperX, 0.0f) == 0)
				profileScale.x = 1.0f;
			else if(taperX > X_EPSILON)
				profileScale.x = 1.0f - percentOfPath * taperX;
			else if(taperX < -X_EPSILON)
				profileScale.x = 1.0f + (1.0 - percentOfPath) * taperX;

			if(CompareFloat(taperX, 0.0f) == 0)
				profileScale.y = 1.0f;
			else if(taperY > X_EPSILON)
				profileScale.y = 1.0f - percentOfPath * taperY;
			else if(taperY < -X_EPSILON)
				profileScale.y = 1.0f + (1.0 - percentOfPath) * taperY;

			if(CompareVector3(profileScale, Vector3(1.0f)) != 0)
				newLayer.Scale(profileScale.x, profileScale.y);

			float twist = twistBegin + twistTotal*percentOfPath;
			if(CompareFloat(twist, 0.0f))
				newLayer.Rotate(Quaternion(0.0f, 0.0f, twist));

			newLayer.Translate(offset);
			newLayer.TranslateTexcoords(Vector2(0.0f, float(step)/float(steps)));

			if(step == 0)
				newLayer.FlipNormals();

			// Append the layer.
			size_t coordsLen = coords.size();
			newLayer.OffsetIndices(coordsLen);

			coords.insert(coords.end(), newLayer.coords.begin(), newLayer.coords.end());
			texcoords.insert(texcoords.end(), newLayer.texcoords.begin(), newLayer.texcoords.end());
			if(percentOfPath <= pathCutBegin || percentOfPath >= pathCutEnd)
				faces.insert(faces.end(), newLayer.faces.begin(), newLayer.faces.end());

			// Fill faces between layer.
			size_t numVerts = newLayer.coords.size();
			Triangle newFace;
			if(step > 0)
			{
				for(size_t i = coordsLen; i < coords.size() - 1; i++)
				{
					newFace.v1 = i;
					newFace.v2 = i - numVerts;
					newFace.v3 = i - numVerts + 1;
					faces.push_back(newFace);

					newFace.v2 = i - numVerts + 1;
					newFace.v3 = i + 1;
					faces.push_back(newFace);
				}

				newFace.v1 = coordsLen - 1;
				newFace.v2 = coordsLen - numVerts;
				newFace.v3 = coordsLen;
				faces.push_back(newFace);

				newFace.v1 = coordsLen + numVerts - 1;
				newFace.v2 = coordsLen - 1;
				newFace.v3 = coordsLen;
				faces.push_back(newFace);
			}

			// Calc the steps for the next iteration.
			if(step < steps)
			{
				step++;
				percentOfPath += percentOfPathMultiplier;
				offset += offsetStepIncrement;
				if(percentOfPath > pathCutEnd)
					done = true;
			}
			else
			{
				done = true;
			}
		}

		// Build the mesh.
		BuildMesh(coords, texcoords, faces, profile.coords.size(), coords.size()/profile.coords.size(), false);
	}

	void Primitive::ExtrudeCircular()
	{
		const float T_PI = 2.0f*X_PI;
		std::vector<Vector3> coords;
		std::vector<Vector2> texcoords;
		std::vector<Triangle> faces;

		int step = 0;
		int steps = 24;

		float twistBegin = this->twistBegin/180.0f*X_PI;
		float twistEnd = this->twistEnd/180.0f*X_PI;
		float twistTotal = twistEnd - twistBegin;

		// If the profile has a lot of twist, add more layers.
		float twistTotalAbs = abs(twistTotal);
		if(twistTotalAbs > X_EPSILON)
		{
			if(twistTotalAbs > X_PI * 1.5f)
				steps *= 2;
			if(twistTotalAbs > X_PI * 3.0f)
				steps *= 2;
		}

		float yPathScale = holeSizeY * 0.5f;
		float pathLength = pathCutEnd - pathCutBegin;
		float totalSkew = skew*2.0f*pathLength;
		float skewStart = pathCutBegin*2.0*skew - skew;
		float xOffsetTopShearXFactor = topShearX *(0.25f + 0.5f * (0.5f - holeSizeY));
		float yShearCompensation = 1.0f + abs(topShearY) * 0.25f;

		float startAngle = T_PI*pathCutBegin*revolutions - topShearY*0.9f;
		float endAngle = T_PI*pathCutEnd*revolutions -  topShearY*0.9f;
		float stepSize = T_PI / stepPerRevolution;

		step = int(startAngle/stepSize);
		int firstStep = step;
		float angle = startAngle;
		float hollow = this->profileHollow;

		// Sanity checks.
		float initialProfileRot = 0.0f;
		if(sides == 3)
		{
			initialProfileRot = X_PI;
			if(hollowSides == 4)
			{
				if(hollow > 0.7f)
					hollow = 0.7f;
				hollow *= M_SQRT1_2;
			}
			else
				hollow *= 0.5f;
		}
		else if(sides == 4)
		{
			initialProfileRot = 0.25f * X_PI;
			if(hollowSides != 4)
				hollow *= M_SQRT1_2;
		}
		else if(sides > 4)
		{
			initialProfileRot = X_PI;
			if(hollowSides == 4)
			{
				if(hollow > 0.7f)
					hollow = 0.7f;
				hollow /= 0.7f;
			}
		}

		bool needEndFaces = false;
		if(CompareFloat(pathCutBegin, 0.0f) != 0 ||
		   CompareFloat(pathCutEnd, 1.0f) != 0)
			needEndFaces = true;
		else if(CompareFloat(taperX, 0.0f) != 0 ||
				CompareFloat(taperY, 0.0f) != 0)
			needEndFaces = true;
		else if(CompareFloat(skew, 0.0f) != 0)
			needEndFaces = true;
		else if(CompareFloat(twistTotal, 0.0f) != 0)
			needEndFaces = true;

		Profile profile(sides, profileStart, profileEnd, hollow, hollowSides, needEndFaces);

		if(CompareFloat(initialProfileRot, 0.0f) != 0)
			profile.Rotate(Quaternion(0.0f, 0.0f, initialProfileRot));

		bool done = false;
		while(!done)
		{
			bool isEndLayer = false;
			if(CompareFloat(angle, startAngle) == 0 ||
				angle >= endAngle - X_EPSILON)
				isEndLayer = true;

			Profile newLayer = profile.Clone(isEndLayer && needEndFaces);

			float xProfileScale = (1.0f - abs(skew)) * holeSizeX;
			float yProfileScale = holeSizeY;

			float percentOfPath = angle / (T_PI * revolutions);
			float percentOfAngles = (angle - startAngle)/(endAngle - startAngle);

			if(taperX > X_EPSILON)
				xProfileScale *= 1.0f - percentOfPath*taperX;
			else if(taperX < -X_EPSILON)
				xProfileScale *= 1.0f + percentOfPath*taperX;

			if(taperY > X_EPSILON)
				yProfileScale *= 1.0f - percentOfPath*taperY;
			else if(taperY < -X_EPSILON)
				yProfileScale *= 1.0f + percentOfPath*taperY;

			if(CompareFloat(xProfileScale, 1.0f) ||
			   CompareFloat(yProfileScale, 1.0f))
			   newLayer.Scale(xProfileScale, yProfileScale);

			float radiusScale = 1.0f;
			if(radius > X_EPSILON)
				radiusScale = 1.0f - radius * percentOfPath;
			else if(radius < -X_EPSILON)
				radiusScale = 1.0f + radius * (1.0f - percentOfPath);

			float twist = twistBegin + twistEnd * percentOfPath;
			Vector3 offset;
			offset.x = 0.5f * (skewStart + totalSkew * percentOfAngles);
			offset.x += sine(angle) * xOffsetTopShearXFactor;
			offset.y = yShearCompensation * cosine(angle) * (0.5f - yPathScale) * radiusScale;
			offset.z = sine(angle + topShearY) *(0.5f - yPathScale) * radiusScale;

			// Next: apply twist rotation into the profile.
			if(CompareFloat(twistTotal, 0.0f) != 0 ||
			   CompareFloat(twistBegin, 0.0f) != 0)
				newLayer.Rotate(Quaternion(0.0f, 0.0f, twist));

			// Orientate the profile relative to his position in the path.
			newLayer.Rotate(Quaternion(angle + topShearY, 0.0f, 0.0f));
			newLayer.Translate(offset);
			newLayer.TranslateTexcoords(Vector2(0.0f, float(step)/float(steps)));

			if(CompareFloat(angle, startAngle) == 0)
				newLayer.FlipNormals();

			// Append the layer.
			size_t coordsLen = coords.size();
			newLayer.OffsetIndices(coordsLen);

			coords.insert(coords.end(), newLayer.coords.begin(), newLayer.coords.end());
			texcoords.insert(texcoords.end(), newLayer.texcoords.begin(), newLayer.texcoords.end());
			if(isEndLayer)
				faces.insert(faces.end(), newLayer.faces.begin(), newLayer.faces.end());

			// Fill faces between layer.
			size_t numVerts = newLayer.coords.size();
			Triangle newFace;
			if(step > firstStep)
			{
				for(size_t i = coordsLen; i < coords.size() - 1; i++)
				{
					newFace.v1 = i;
					newFace.v2 = i - numVerts;
					newFace.v3 = i - numVerts + 1;
					faces.push_back(newFace);

					newFace.v2 = i - numVerts + 1;
					newFace.v3 = i + 1;
					faces.push_back(newFace);
				}

				newFace.v1 = coordsLen - 1;
				newFace.v2 = coordsLen - numVerts;
				newFace.v3 = coordsLen;
				faces.push_back(newFace);

				newFace.v1 = coordsLen + numVerts - 1;
				newFace.v2 = coordsLen - 1;
				newFace.v3 = coordsLen;
				faces.push_back(newFace);
			}

			// Calculate elements for next iteration.
			if(angle >= endAngle - X_EPSILON)
				done = true;
			else
			{
				step++;
				angle = stepSize*step;
				if(angle > endAngle)
					angle = endAngle;
			}
		}

		BuildMesh(coords, texcoords, faces, profile.coords.size(), coords.size()/profile.coords.size(), false);
	}

	void Primitive::BuildMesh(const std::vector<Vector3> &coords, const std::vector<Vector2> &texcoords,
			const std::vector<Triangle> &faces, int width, int height, bool flip)
	{
		size_t numvertices = coords.size();
		size_t numtriangles = faces.size();

		// Transform the positions.
		Matrix3 trans = QRotMatrix3(rotation)*ScaleMatrix3(scale);
		std::vector<Vector3> positions;
		positions.reserve(numvertices);
		for(size_t i = 0; i < numvertices; i++)
		{
			positions.push_back(trans*coords[i]);
		}

		std::vector<Vector3> normals;
		normals.resize(numvertices, Vector3(0.0f));

		// Calculate the normals.
		for(size_t i = 0; i < numtriangles; i++)
		{
			const Triangle &face = faces[i];
			const Vector3 &p1 = positions[face.v1];
			const Vector3 &p2 = positions[face.v2];
			const Vector3 &p3 = positions[face.v3];
			Vector3 u = (p2 - p1).Normalize();
			Vector3 v = (p3 - p1).Normalize();
			Vector3 n = u.Cross(v).Normalize();
			if(flip)
				n *= -1.0f;

			normals[face.v1] += n;
			normals[face.v2] += n;
			normals[face.v3] += n;
		}

		// Fix profile discontinuities.
		for(int y = 0; y < height; y++)
		{
			// Assume the last is always the end.
			size_t start = y*width + 1;
			size_t end = (y+1)*width - 1;
			if(CompareVector3(coords[start], coords[end]) != 0)
				continue;

			// Add the two normals.
			Vector3 n = normals[start] + normals[end];
			normals[start] = n;
			normals[end] = n;
		}

		// Fix path discontinuities.
		for(int x = 0; x < width; x++)
		{
			// Assume the last is always the end.
			size_t start = x;
			size_t end = (height-1)*width + x;
			if(CompareVector3(coords[start], coords[end]) != 0)
				continue;

			// Add the two normals.
			Vector3 n = normals[start] + normals[end];
			normals[start] = n;
			normals[end] = n;
		}

		// Normalize the normals.
		for(size_t i = 0; i < numvertices; i++)
		{
			normals[i] = normals[i].Normalize();
		}

		Ogre::VertexDeclaration *declaration = mesh->sharedVertexData->vertexDeclaration;
		Ogre::VertexBufferBinding *bind = mesh->sharedVertexData->vertexBufferBinding;
		mesh->sharedVertexData->vertexCount = numvertices;

		// Reallocate the position buffer.
		size_t vertexSize = declaration->getVertexSize(0);
		Ogre::HardwareVertexBufferSharedPtr vbuf =
				Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				vertexSize, numvertices, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		bind->setBinding(0, vbuf);

		// Write the positions.
		float *positionData = new float[numvertices*3];
		for(size_t i = 0; i < numvertices; i++)
		{
			positionData[i*3] = positions[i].x;
			positionData[i*3+1] = positions[i].y;
			positionData[i*3+2] = positions[i].z;
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), positionData, true);
		delete [] positionData;

		// Reallocate the normal buffer.
		vertexSize = declaration->getVertexSize(1);
		vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				vertexSize, numvertices, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		bind->setBinding(1, vbuf);

		// Write the normals.
		float *normalData = new float[numvertices*3];
		for(size_t i = 0; i < numvertices; i++)
		{
			const Vector3 &normal = normals[i];
			normalData[i*3] = normal.x;
			normalData[i*3+1] = normal.y;
			normalData[i*3+2] = normal.z;
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), normalData, true);
		delete [] normalData;

		// Reallocate the texcoords.
		vertexSize = declaration->getVertexSize(2);
		vbuf =	Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				vertexSize, numvertices, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		bind->setBinding(2, vbuf);

		// Write the texcoords
		float *texData = new float[numvertices*2];
		for(size_t i = 0; i < numvertices; i++)
		{
			const Vector2 &tex = texcoords[i];
			texData[i*2] = tex.x;
			texData[i*2+1] = tex.y;
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), texData, true);
		delete [] texData;

		// Reallocate the index buffer.
		size_t numindices = numtriangles*3;
		Ogre::HardwareIndexBufferSharedPtr ibuf =
				Ogre::HardwareBufferManager::getSingleton().
				createIndexBuffer(
						Ogre::HardwareIndexBuffer::IT_16BIT,
						numindices,
						Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);



		// Upload the faces.
		unsigned short *indexData = new unsigned short[numtriangles*3];
		for(size_t i = 0; i < numtriangles; i++)
		{
			if(flip)
			{
				indexData[i*3] = faces[i].v3;
				indexData[i*3+1] = faces[i].v2;
				indexData[i*3+2] = faces[i].v1;
			}
			else
			{
				indexData[i*3] = faces[i].v1;
				indexData[i*3+1] = faces[i].v2;
				indexData[i*3+2] = faces[i].v3;
			}
		}

		ibuf->writeData(0, ibuf->getSizeInBytes(), indexData, true);
		delete [] indexData;

		// Bind the index buffer.
		Ogre::SubMesh *sub = mesh->getSubMesh(0);
		sub->useSharedVertices = true;
		sub->indexData->indexBuffer = ibuf;
		sub->indexData->indexCount = numindices;
		sub->indexData->indexStart = 0;

		// Load the mesh.
		mesh->load();
	}

	inline size_t MapIndex(size_t width, size_t height, int x, int y, bool rs, bool rt)
	{
		if(rs)
		{
			// Repeat the x coordinate.
			if(x < 0)
				x = width + x%width;
			if(x >= width)
			   x %= width;
		}
		else
		{
			// Clampt the y coordinate.
			if(x < 0)
				x = 0;
			if(x >= width)
				x = width-1;
		}

		if(rt)
		{
			// Repeat the y coordinate.
			if(y < 0)
				y = height + y%height;
			if(y >= height)
			   y %= height;
		}
		else
		{
			// Clamp the y coordinate.
			if(y < 0)
				y = 0;
			if(y >= height)
				y = height-1;
		}

		return width*y + x;
	}

	inline void SculptSize(int type, int sculptWidth, int sculptHeight, int &width, int &height)
	{
		switch(type)
		{
		case SCT_SPHERE:
			width = (sculptWidth >> 1) + 1;
			height = (sculptWidth >> 1) + 1;
			break;
		case SCT_CYLINDER:
			width = (sculptWidth >> 1) + 1;
			height = (sculptHeight >> 1) + 1;
			break;
		case SCT_PLANE:
			width = (sculptWidth >> 1) + 1;
			height = (sculptHeight >> 1) + 1;
			break;
		case SCT_TORUS:
			width = (sculptWidth >> 1) + 1;
			height = (sculptHeight >> 1) + 1;
			break;
		default:
			width = sculptWidth >> 1;
			height = sculptHeight >> 1;
			break;
		}
	}

	inline size_t SculptIndex(int type, size_t sculptWidth, size_t sculptHeight, size_t width, size_t height, int x, int y)
	{
		int nx = sculptWidth*x/(width-1);
		int ny = sculptHeight*y/(height-1);
		switch(type)
		{
		case SCT_SPHERE:
			if(y == 0)
				return MapIndex(sculptWidth, sculptHeight, sculptWidth/2, 0, true, false);
			else if(y == height - 1)
				return MapIndex(sculptWidth, sculptHeight, sculptWidth/2, sculptHeight-1, true, false);
			return MapIndex(sculptWidth, sculptHeight, nx, ny, true, false);
		case SCT_CYLINDER:
			return MapIndex(sculptWidth, sculptHeight, nx, ny, true, false);
		case SCT_PLANE:
			return MapIndex(sculptWidth, sculptHeight, nx, ny, false, false);
		case SCT_TORUS:
		default:
			return MapIndex(sculptWidth, sculptHeight, nx, ny, true, true);
		}
	}

	inline Vector3 SculptNormal(int type, std::vector<Vector3> &vertices, size_t sculptWidth, size_t sculptHeight, size_t width, size_t height, int x, int y)
	{
		Vector3 tan;
		Vector3 bi;
		int nx = x + 1;
		int px = x - 1;
		int ny = y + 1;
		int py = y - 1;
		bool rs = true, rt = true;
		switch(type)
		{
		case SCT_CYLINDER:
			if(x == width - 1)
				nx = 1;
			else if(x == 0)
				px = width - 2;
			rs = true;
			rt = false;
			break;
		case SCT_SPHERE:
			if(x == width - 1)
				nx = 1;
			else if(x == 0)
				px = width - 2;
			rs = true;
			rt = false;
			break;
		case SCT_TORUS:
			if(x == width - 1)
				nx = 1;
			else if(x == 0)
				px = width - 2;
			if(y == height - 1)
				ny = 1;
			else if(y == 0)
				py = height - 2;
			rs = true;
			rt = true;
			break;
		default:
			break;
		}
		tan = (vertices[MapIndex(width, height, nx, y, rs, rt)]
			   - vertices[MapIndex(width, height, px, y, rs, rt)]).Normalize();
		bi = (vertices[MapIndex(width, height, x, ny, rs, rt)]
			   - vertices[MapIndex(width, height, x, py, rs, rt)]).Normalize();
		return bi.Cross(tan);
	}

	void Primitive::BuildSculpture()
	{
		// Create the transform.
		Matrix3 trans = QRotMatrix3(rotation)*ScaleMatrix3(scale);

		// Calculate the actual sculpt size.
		int sculptWidth = sculpture->GetWidth();
		int sculptHeight = sculpture->GetHeight();
		int width;
		int height;
		SculptSize(sculptureType, sculptWidth, sculptHeight, width, height);

		// Calculate the number of vertices.
		size_t numvertices = width*height;

		// Allocate the vertices.
		std::vector<Vector3> vertices;
		vertices.reserve(numvertices);

		// Read the vertices.
		u8 *sculptData;
		int stride = 0;
		switch(sculpture->GetFormat())
		{
		case ITexture::TF_RGB8:
		{
			// Read the sculpture.
			size_t levelSize = sculptWidth*sculptHeight*3;
			sculptData = new u8[levelSize];
			memset(sculptData, 0, levelSize);
			stride = 3;
			if(!sculpture->ReadLevel(0, levelSize, sculptData))
			{
				LogConsole->Error("Failed to read sculpt texture\n");
				delete [] sculptData;
				return;
			}
		}
			break;
		case ITexture::TF_RGBA8:
		{
			size_t levelSize = sculptWidth*sculptHeight*4;
			sculptData = new u8[levelSize];
			stride = 4;
			if(!sculpture->ReadLevel(0, levelSize, sculptData))
			{
				LogConsole->Error("Failed to read sculpt texture\n");
				delete [] sculptData;
				return;
			}
		}
			break;
		default:
			LogConsole->Error("Unsupported sculpt texture format %d.", sculpture->GetFormat());
			return;
		}

		// Convert the sculpture data.
		for(int y = 0; y < height; y++)
		{
			for(int x = 0; x < width; x++)
			{
				size_t index = SculptIndex(sculptureType, sculptWidth, sculptHeight,
						width, height, x, y)*stride;
				Vector3 v;
				v.x = float(sculptData[index+2] - 127)/128.0f*0.5f;
				v.y = float(sculptData[index+1] - 127)/128.0f*0.5f;
				v.z = float(sculptData[index] -127)/128.0f*0.5f;
				vertices.push_back(trans*v);
			}
		}

		delete [] sculptData;

		// Get the vertex data.
		Ogre::VertexDeclaration *declaration = mesh->sharedVertexData->vertexDeclaration;
		Ogre::VertexBufferBinding *bind = mesh->sharedVertexData->vertexBufferBinding;
		mesh->sharedVertexData->vertexCount = numvertices;

		// Reallocate the position buffer.
		size_t vertexSize = declaration->getVertexSize(0);
		Ogre::HardwareVertexBufferSharedPtr vbuf =
				Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				vertexSize, numvertices, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		bind->setBinding(0, vbuf);

		// Write the positions.
		float *positionData = new float[numvertices*3];
		for(size_t i = 0; i < numvertices; i++)
		{
			positionData[i*3] = vertices[i].x;
			positionData[i*3+1] = vertices[i].y;
			positionData[i*3+2] = vertices[i].z;
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), positionData, true);
		delete [] positionData;

		// Reallocate the normal buffer.
		vertexSize = declaration->getVertexSize(1);
		vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				vertexSize, numvertices, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		bind->setBinding(1, vbuf);

		// Calculate and write the normals.
		float *normalData = new float[numvertices*3];
		for(int y = 0; y < height; y++)
		{
			float *line = normalData + y*width*3;
			for(int x = 0; x < width; x++)
			{
				Vector3 n = SculptNormal(sculptureType, vertices, sculptWidth, sculptHeight, width, height, x, y);
				line[x*3] = n.x;
				line[x*3+1] = n.y;
				line[x*3+2] = n.z;
			}
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), normalData, true);
		delete [] normalData;

		// Reallocate the texcoords.
		vertexSize = declaration->getVertexSize(2);
		vbuf =	Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				vertexSize, numvertices, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		bind->setBinding(2, vbuf);

		// Calculate and write the texcoords
		float *texData = new float[numvertices*2];
		for(int y = 0; y < height; y++)
		{
			float *line = texData + y*width*2;
			for(int x = 0; x < width; x++)
			{
				line[x*2] = float(x)/float(width-1);
				line[x*2+1] = float(y)/float(height-1);
			}
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), texData, true);
		delete [] texData;

		// Reallocate the index buffer.
		size_t numquads = (width-1)*(height-1);
		size_t numindices = numquads*6;
		Ogre::HardwareIndexBufferSharedPtr ibuf =
				Ogre::HardwareBufferManager::getSingleton().
				createIndexBuffer(
						Ogre::HardwareIndexBuffer::IT_16BIT,
						numindices,
						Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		// Calculate and upload the indices.
		unsigned short *indices = new unsigned short[numindices];
		for(int y = 0; y < height - 1; y++)
		{
			unsigned short *line = indices + y*(width-1)*6;
			for(int x = 0; x < width - 1; x++)
			{
				line[x*6+2] = y*width + x;
				line[x*6+1] = y*width + x + 1;
				line[x*6] = (y+1)*width + x;

				line[x*6+5] = y*width + x + 1;
				line[x*6+4] = (y+1)*width + x + 1;
				line[x*6+3] = (y+1)*width + x;
			}
		}

		ibuf->writeData(0, ibuf->getSizeInBytes(), indices, true);
		delete [] indices;

		// Bind the index buffer.
		Ogre::SubMesh *sub = mesh->getSubMesh(0);
		sub->useSharedVertices = true;
		sub->indexData->indexBuffer = ibuf;
		sub->indexData->indexCount = numindices;
		sub->indexData->indexStart = 0;

		// Load the mesh.
		mesh->load();
	}

}; // namespace XernOgre
